//go:build darwin || windows
// +build darwin windows

package grpc

import (
	"context"
	"crypto/tls"
	"net"
	"time"

	apimd "github.com/go-kratos/kratos/v2/api/metadata"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport"
	"google.golang.org/grpc"
	"google.golang.org/grpc/admin"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"
	"google.golang.org/grpc/reflection"
)

var (
	_ transport.Server = (*grpcServer)(nil)
)

// grpcServer is a gRPC server wrapper.
type grpcServer struct {
	*grpc.Server
	baseCtx      context.Context
	tlsConf      *tls.Config
	lis          net.Listener
	err          error
	network      string
	address      string
	timeout      time.Duration
	grpcOpts     []grpc.ServerOption
	health       *health.Server
	customHealth bool
	metadata     *apimd.Server
	adminClean   func()
}

// NewServer creates a gRPC server by options.
func NewGrpcServer(opts ...ServerOption) *grpcServer {
	srv := &grpcServer{
		baseCtx: context.Background(),
		network: "tcp",
		address: ":0",
		timeout: 1 * time.Second,
		health:  health.NewServer(),
	}
	for _, o := range opts {
		o(srv)
	}

	grpcOpts := []grpc.ServerOption{}
	if srv.tlsConf != nil {
		grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(srv.tlsConf)))
	}
	if len(srv.grpcOpts) > 0 {
		grpcOpts = append(grpcOpts, srv.grpcOpts...)
	}
	srv.Server = grpc.NewServer(grpcOpts...)
	srv.metadata = apimd.NewServer(srv.Server)
	// internal register
	if !srv.customHealth {
		grpc_health_v1.RegisterHealthServer(srv.Server, srv.health)
	}
	apimd.RegisterMetadataServer(srv.Server, srv.metadata)
	reflection.Register(srv.Server)
	// admin register
	srv.adminClean, _ = admin.Register(srv.Server)
	return srv
}

// Start start the gRPC server.
func (s *grpcServer) Start(ctx context.Context) error {
	if err := s.listenAndEndpoint(); err != nil {
		log.Error(err)
		return s.err
	}
	s.baseCtx = ctx
	log.Infof("[gRPC] server listening on: %s", s.lis.Addr().String())
	s.health.Resume()
	return s.Serve(s.lis)
}

// Stop stop the gRPC server.
func (s *grpcServer) Stop(ctx context.Context) error {
	if s.adminClean != nil {
		s.adminClean()
	}
	s.health.Shutdown()
	s.GracefulStop()
	log.Info("[gRPC] server stopping")
	return nil
}

func (s *grpcServer) listenAndEndpoint() error {
	var (
		lis net.Listener
		err error
	)

	if s.lis != nil {
		return s.err
	}

	lis, err = Listen(s.network, s.address)
	if err != nil {
		s.err = err
		return err
	}
	s.lis = lis

	return s.err
}

// ServerOption is gRPC server option.
type ServerOption func(o *grpcServer)

// Network with server network.
func Network(network string) ServerOption {
	return func(s *grpcServer) {
		s.network = network
	}
}

// Address with server address.
func Address(addr string) ServerOption {
	return func(s *grpcServer) {
		s.address = addr
	}
}

// Timeout with server timeout.
func Timeout(timeout time.Duration) ServerOption {
	return func(s *grpcServer) {
		s.timeout = timeout
	}
}

// Logger with server logger.
// Deprecated: use global logger instead.
func Logger(logger log.Logger) ServerOption {
	return func(s *grpcServer) {}
}

// CustomHealth Checks server.
func CustomHealth() ServerOption {
	return func(s *grpcServer) {
		s.customHealth = true
	}
}

// TLSConfig with TLS config.
func TLSConfig(c *tls.Config) ServerOption {
	return func(s *grpcServer) {
		s.tlsConf = c
	}
}

// Listener with server lis
func Listener(lis net.Listener) ServerOption {
	return func(s *grpcServer) {
		s.lis = lis
	}
}

// Options with grpc options.
func Options(opts ...grpc.ServerOption) ServerOption {
	return func(s *grpcServer) {
		s.grpcOpts = opts
	}
}
